﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Frames;
using RayDen.Library.Entity.Scene;

namespace RayDen.Library.Entity.SceneGraph {

    public class SceneGraphService : DependentComponentBase
    {
        private Dictionary<Type, Action<IFrameElement>> frameParsers;
        private SceneGeometryInfo scene;
        protected MaterialInfo[] mats;
        private SceneGraphInfo sceneGraph;
        private List<CameraInfo> cameras;
        private List<LightsourceInfo> lights;
        private List<VolumeMaterialInfo> volumeMats;
        public SceneGraphService()
        {
            sceneGraph = new SceneGraphInfo();
            frameParsers = new Dictionary<Type, Action<IFrameElement>>();
            cameras = new List<CameraInfo>();
            frameParsers.Add(typeof(FrameCamera), cameraItem =>
            {
                var item = (FrameCamera)cameraItem;
                cameras.Add(new CameraInfo(item));
                //var transform = Transform.LookAt(item.Position, item.Target, item.Up);
                //CameraNode cam = new CameraNode(transform, transform.GetInverse(), );
                /*Camera = new PinholeCamera(width, height){ Fov = 40f};
                Camera.LookAt(item.Position, item.Target, item.Up);*/
            });



            frameParsers.Add(typeof(FrameObjFileReference), item =>
            {
                var objFileRef = (FrameObjFileReference)item;
                var loader = BaseContainer.Get<IGeometryLoader>("LoadObj");
                    //new GlobalIndexObjLoader();
                scene = loader.Load(objFileRef.ObjFilePath, objFileRef.InvertNormals);
                var mload = BaseContainer.Get<IMaterialLoader>("LoadMtl");
                    //new MaterialLoader();
                mats = mload.LoadMaterials(objFileRef.MtlFilePath);

                foreach (var materialInfo in mats) {
                    if (materialInfo.Name.ToLower().Contains("glass") ||
                         materialInfo.Name.ToLower().Contains("wire_134006006")
                         ) {
                        materialInfo.Kt = materialInfo.DiffuseReflectance;
                        materialInfo.DiffuseReflectance = RgbSpectrum.ZeroSpectrum();

                        materialInfo.MediumInfo = MediumInfo.Glass;
                    }
                    if (materialInfo.Name.ToLower().Contains("metal")) {
                        materialInfo.SpecularReflectance = materialInfo.DiffuseReflectance;
                    }
                }
            });

/*
            frameParsers.Add(typeof(Frame3DsFileReference), item =>
            {
                var sceneFileRef = (Frame3DsFileReference)item;
                var loader = new SceneLoader();
                scene = loader.Load(sceneFileRef.FilePath);
                mats = loader.LoadMaterials(sceneFileRef.FilePath);

                foreach (var materialInfo in mats) {
                    if (materialInfo.Name.ToLower().Contains("glass") ||
                         materialInfo.Name.ToLower().Contains("wire_134006006")
                         ) {
                        materialInfo.Kt = materialInfo.Kd;
                        materialInfo.Kd = RgbSpectrum.ZeroSpectrum();

                        materialInfo.MediumInfo = Glass;
                    }
                    if (materialInfo.Name.ToLower().Contains("metal")) {
                        materialInfo.Kr = materialInfo.Kd;
                    }
                }
                if (scene.Cameras != null && scene.Cameras.Any()) {
                    Camera = new BasicPerspectiveCamera(scene.Cameras[0].Position, scene.Cameras[0].Direction, scene.Cameras[0].Up, width, height) { Fov = scene.Cameras[0].Fov };
                }
            });
*/

            

            frameParsers.Add(typeof(FrameLightsource), item =>
            {
                if (lights == null) {
                    lights = new List<LightsourceInfo>();
                }
                var lightsource = (FrameLightsource)item;
                lights.Add(new LightsourceInfo(lightsource));
            });



            frameParsers.Add(typeof(FrameVolumeArea) , item =>
            {
                if (volumeMats == null)
                {
                    volumeMats = new List<VolumeMaterialInfo>();
                }
                var vm = (FrameVolumeArea) item;
                volumeMats.Add(new VolumeMaterialInfo()
                {
                    Name = vm.MeshName,
                    Outscattering = vm.Emittance,
                    Inscattering = vm.Scattering,
                    Absorbtion = vm.Absorbance
                });
            });
        }

        public SceneGraphInfo SceneGraph
        {
            get { return sceneGraph; }
        }

        public void OpenFrame(FrameDescription frameDescription)
        {
           
            //width = frame.Get<int>("ImageWidth");
            //height = frame.Get<int>("ImageHeight");

            if (!string.IsNullOrWhiteSpace(frameDescription.WorkingDir))
                Directory.SetCurrentDirectory(frameDescription.WorkingDir);
            foreach (var frameElement in frameDescription.Elements.Where(frameElement => frameParsers.ContainsKey(frameElement.GetType())))
            {
                frameParsers[frameElement.GetType()](frameElement);
            }

            this.sceneGraph = this.CreateSceneGraph(scene, Transform.Scale(1f, 1f, 1f));
            this.SceneGraph.Parameters = frameDescription;
            this.SceneGraph.SceneName = frameDescription.FrameName;
        }



        private SceneGraphInfo CreateSceneGraph( SceneGeometryInfo geoInfo,
                                                Transform worldTransform, 
                                                SceneGraphChangesType changes = SceneGraphChangesType.FullGraph ) {
            var scene = new SceneGraphInfo() { RootNode = new SceneGraphRootNode() };

            var cams = (cameras.Select(
                cameraInfo =>
                new {
                    cameraInfo,
                    camTransform = Transform.LookAt((Point)cameraInfo.Position, (Point)cameraInfo.Direction, cameraInfo.Up)
                })
                                   .Select(
                                       @t =>
                                       new CameraNode(@t.camTransform, @t.camTransform.GetInverse(), @t.cameraInfo) {
                                           Name = @t.cameraInfo.CameraName
                                       })).Cast<SceneGraphElement>().ToList();

            var scg = new SceneGeometryDataNode(worldTransform, worldTransform.GetInverse(), geoInfo);
            scene.RootNode.Children.Add(scg);

            var geoGroup = new GeometryGroupNode(worldTransform, worldTransform.GetInverse(), scg.Id) {FindRoot = scene.RootNode};
            scene.RootNode.Children.Add(geoGroup);

            var elements = geoInfo.Geometry.Select(
                geometryInfo => new { geometryInfo, geoTransform = new Transform()} )
                                      .Select(
                                          @t =>
                                          new GeometryElementNode(@t.geoTransform, @t.geoTransform.GetInverse(),
                                              @t.geometryInfo)).ToArray();

            geoGroup.Children.AddRange(elements);

            geoGroup.Children.AddRange(elements.Select(geoData => new GeometryInstanceNode(geoData.ObjectToWorld, geoData.WorldToObject, geoData.Id){ FindRoot = scg }));
            scene.RootNode.Children.AddRange(cams);
            scene.RootNode.Children.AddRange(mats.Select(item => new SceneMaterialDataNode(item)));
            scene.RootNode.Children.AddRange(this.lights.Select(item=>new LightsourceNode(null, null, item)));
            if (volumeMats != null && volumeMats.Any())
                scene.RootNode.Children.AddRange(volumeMats.Select(item=>new VolumeNode<VolumeMaterialInfo>(null, null, item)));
            scene.RootNode.WorldToObject = worldTransform;

            return scene;
        }
    }
}
